package org.spamforces.web.page;

import com.google.inject.Inject;
import org.jacuzzi.core.DatabaseException;
import org.nocturne.annotation.Action;
import org.nocturne.annotation.Parameter;
import org.nocturne.link.Link;
import org.nocturne.validation.ValidationException;
import org.nocturne.validation.Validator;
import org.spamforces.dao.EmailDao;
import org.spamforces.model.Broadcast;
import org.spamforces.model.BroadcastStatus;
import org.spamforces.model.RecipientProvider;
import org.spamforces.model.Recipient;
import org.spamforces.utils.BroadcastHelper;
import org.spamforces.utils.XMLHelper;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLStreamException;
import java.io.ByteArrayInputStream;
import java.text.DateFormat;
import java.util.*;

@Link("broadcast")
public class BroadcastEditPage extends BroadcastPage {
    @Inject
    EmailDao emailDao;

    @Parameter
    private long id;

    @Parameter(stripMode = Parameter.StripMode.NONE)
    private String subjectTemplate;

    @Parameter(stripMode = Parameter.StripMode.NONE)
    private String bodyTemplate;

    @Parameter(stripMode = Parameter.StripMode.NONE)
    private String templateParams;

    @Parameter(stripMode = Parameter.StripMode.NONE)
    private String testEmailProviderResponse;

    @Parameter(stripMode = Parameter.StripMode.NONE)
    private String emailProvidersId;

    @Parameter(stripMode = Parameter.StripMode.NONE)
    private String scheduledTime;

    @Override
    public String getTitle() {
        return $("Edit Broadcast");
    }

    @Override
    public void action() {
        try {
            Broadcast broadcast = broadcastDao.find(id);
            BroadcastForView broadcastForView = toFullView(broadcast);
            put("broadcast", broadcastForView);
            put("scheduledTime", DateFormat.getInstance().format(
                    broadcast.getScheduledTime() == null ? broadcastDao.findNow() : broadcast.getScheduledTime()
            ));
            List<RecipientProvider> recipientProviders = new ArrayList<>();
            for (RecipientProvider recipientProvider : recipientProviderDao.findAll()) {
                if (!broadcastForView.getRecipientProviders().contains(recipientProvider)) {
                    recipientProviders.add(recipientProvider);
                }
            }
            put("providers", recipientProviders);
        } catch (DatabaseException ignored) {
            // No operations.
        }
    }

    private Map<String, String> params;

    class FreeMarkerValidator extends Validator {
        @Override
        public void run(String value) throws ValidationException {
            if (params == null) {
                return;
            }

            try {
                BroadcastHelper.testTemplate(value, params);
            } catch (Exception e) {
                throw new ValidationException(e.getMessage());
            }
        }
    }

    @Action("edit")
    public void onEdit() {
        skipTemplate();

        addValidator("emailProvidersId", new Validator() {
            @Override
            public void run(String value) throws ValidationException {
                String[] s = value.split(",");
                for (String x : s) {
                    String y = x.trim();
                    try {
                        long id = Integer.parseInt(y);
                        RecipientProvider ep = recipientProviderDao.find(id);
                        if (ep == null) {
                            throw new ValidationException(
                                    "Invalid email provider id");
                        }
                    } catch (Exception e) {
                        throw new ValidationException("Invalid email provider id");
                    }
                }
            }
        });

        addValidator("templateParams", new Validator() {
            @Override
            public void run(String value) throws ValidationException {
                try {
                    params = BroadcastHelper.extractMap(value);
                } catch (IllegalArgumentException e) {
                    throw new ValidationException(e.getMessage());
                }

                if (params == null) {
                    params = new HashMap<>();
                    throw new ValidationException(
                            "Params must be in 'key=value' form, separated with line breaks");
                }
            }
        });

        addValidator("subjectTemplate", new FreeMarkerValidator());
        addValidator("bodyTemplate", new FreeMarkerValidator());

        addValidator("testEmailProviderResponse", new Validator() {
            @Override
            public void run(String value) throws ValidationException {
                ByteArrayInputStream bytes = new ByteArrayInputStream(value
                        .getBytes());
                try {
                    XMLHelper.processXML(bytes);
                } catch (XMLStreamException | FactoryConfigurationError e) {
                    throw new ValidationException(e.getMessage());
                }
            }
        });
        if (runValidationAndPrintErrors()) {
            Broadcast broadcast = broadcastDao.find(id);
            if (broadcast != null) {
                broadcast.setBodyTemplate(bodyTemplate);
                broadcast.setTemplateParams(templateParams);
                broadcast.setSubjectTemplate(subjectTemplate);
                broadcast.setTestEmailProviderResponse(testEmailProviderResponse);
                broadcast.setEmailProvidersId(emailProvidersId);
                broadcastDao.update(broadcast);
                setMessage("broadcast updated");
                printJson("success", "true");
            }
        }
    }

    @Action("preview")
    public void onPreview() {
        skipTemplate();
        Broadcast b = broadcastDao.find(id);
        if (b == null) {
            return;
        }

        try {
            Map<String, String> params = BroadcastHelper.extractMap(b
                    .getTemplateParams());
            String previewSubject = BroadcastHelper.testTemplate(
                    b.getSubjectTemplate(), params);
            String previewBody = BroadcastHelper.testTemplate(
                    b.getBodyTemplate(), params);
            printJson("success", "true", "previewSubject", previewSubject,
                    "previewBody", previewBody);
        } catch (Exception ignored) {
            // No operations.
        }
    }

    @Action("create")
    public void onCreate() {
        skipTemplate();
        Broadcast b = broadcastDao.find(id);
        try {
            Broadcast newB = new Broadcast();
            if (b != null) {
                newB.setParentBroadcastId(b.getId());
                newB.setSubjectTemplate(b.getSubjectTemplate());
                newB.setBodyTemplate(b.getBodyTemplate());
                newB.setTemplateParams(b.getTemplateParams());
                newB.setEmailProvidersId(b.getEmailProvidersId());
                newB.setTestEmailProviderResponse(b
                        .getTestEmailProviderResponse());
            } else {
                newB.setSubjectTemplate("Subject template (ignored for SMS)");
                newB.setBodyTemplate("Body template");
                newB.setTemplateParams("paramA=valueA\r\nparamB=valueB");
                newB.setEmailProvidersId("");
                newB.setTestEmailProviderResponse("<recipients><recipient email=\"\" phone=\"\"/></recipients>");
            }
            newB.setStatus(BroadcastStatus.SCHEDULING);
            newB.setUserId(getUser().getId());
            broadcastDao.save(newB);
            setMessage("broadcast created");
            printJson("success", "true", "id", Long.toString(newB.getId()));
        } catch (Exception e) {
            printJson("success", "false", "error", e.getMessage());
        }
    }

    @Action("delete")
    public void onDelete() {
        skipTemplate();
        Broadcast b = broadcastDao.find(id);
        if (b == null) {
            printJson("success", "false", "error", "No such broadcast");
            return;
        }
        if (b.getUserId() != getUser().getId()) {
            printJson("success", "false", "error", "Access denied");
            return;
        }
        try {
            broadcastDao.delete(b);
            setMessage("broadcast deleted");
            printJson("success", "true", "id", Long.toString(b.getId()));
        } catch (Exception e) {
            printJson("success", "false", "error", e.getMessage());
        }
    }

    @Action("test")
    public void onTest() {
        skipTemplate();
        Broadcast broadcast = broadcastDao.find(id);
        if (broadcast == null) {
            printJson("success", "false", "error", "No such broadcast");
            return;
        }
        try {
            Map<Recipient, Map<String, String>> users = XMLHelper
                    .processXML(new ByteArrayInputStream(broadcast.getTestEmailProviderResponse().getBytes()));
            BroadcastHelper.scheduleBroadcast(broadcast, users, emailDao, smsDao, false);
            printJson("success", "true");
        } catch (Exception e) {
            e.printStackTrace();
            printJson("success", "false", "error", e.getMessage());
        }
    }

    @Action("schedule")
    public void onSchedule() {
        skipTemplate();
        Broadcast broadcast = broadcastDao.find(id);
        if (broadcast == null) {
            printJson("success", "false", "error", "No such broadcast");
            return;
        }
        try {
            Date time = DateFormat.getInstance().parse(scheduledTime);
            if (time.before(broadcastDao.findNow())) {
                printJson("success", "false");
                printJson("error", "Schedule time must be after NOW()");
                return;
            }
            broadcast.setScheduledTime(time);
            broadcast.setStatus(BroadcastStatus.SCHEDULED);
            broadcastDao.update(broadcast);
            printJson("success", "true");
        } catch (Exception e) {
            e.printStackTrace();
            printJson("success", "false", "error", e.getMessage());
        }
    }
}
